1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.hiprenderer.shader.shader; 12 public import hip.hiprenderer.shader.shadervar : 13 ShaderHint, ShaderVariablesLayout, ShaderVar; 14 15 import hip.api.data.commons:IReloadable; 16 import hip.api.renderer.texture; 17 import hip.math.matrix; 18 import hip.error.handler; 19 import hip.hiprenderer.shader.shadervar; 20 import hip.hiprenderer.backend.gl.glshader; 21 import hip.hiprenderer.shader; 22 import hip.hiprenderer.renderer; 23 import hip.util.file; 24 import hip.util.string:indexOf; 25 26 enum ShaderStatus 27 { 28 SUCCESS, 29 VERTEX_COMPILATION_ERROR, 30 FRAGMENT_COMPILATION_ERROR, 31 LINK_ERROR, 32 UNKNOWN_ERROR 33 } 34 35 enum ShaderTypes 36 { 37 VERTEX, 38 FRAGMENT, 39 GEOMETRY, //Unsupported yet 40 NONE 41 } 42 43 enum HipShaderPresets 44 { 45 DEFAULT, 46 FRAME_BUFFER, 47 GEOMETRY_BATCH, 48 SPRITE_BATCH, 49 BITMAP_TEXT, 50 NONE 51 } 52 53 /** 54 * This interface is currrently a Shader factory. 55 */ 56 interface IShader 57 { 58 VertexShader createVertexShader(); 59 FragmentShader createFragmentShader(); 60 ShaderProgram createShaderProgram(); 61 62 bool compileShader(FragmentShader fs, string shaderSource); 63 bool compileShader(VertexShader vs, string shaderSource); 64 bool linkProgram(ref ShaderProgram program, VertexShader vs, FragmentShader fs); 65 66 void setBlending(ShaderProgram prog, HipBlendFunction src, HipBlendFunction dst, HipBlendEquation eq); 67 void bind(ShaderProgram program); 68 void unbind(ShaderProgram program); 69 void sendVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset); 70 int getId(ref ShaderProgram prog, string name); 71 72 73 ///Used as intermediary for deleting non program intermediary in opengl 74 void deleteShader(FragmentShader* fs); 75 ///Used as intermediary for deleting non program intermediary in opengl 76 void deleteShader(VertexShader* vs); 77 78 void createVariablesBlock(ref ShaderVariablesLayout layout); 79 bool setShaderVar(ShaderVar* sv, ShaderProgram prog, void* value); 80 void sendVars(ref ShaderProgram prog, ShaderVariablesLayout[string] layouts); 81 82 /** 83 * Each graphics API has its own way to bind array of textures, thus, this version was required. 84 */ 85 void bindArrayOfTextures(ref ShaderProgram prog, IHipTexture[] textures, string varName); 86 void dispose(ref ShaderProgram); 87 88 void onRenderFrameEnd(ShaderProgram program); 89 } 90 91 abstract class VertexShader 92 { 93 abstract string getDefaultVertex(); 94 abstract string getFrameBufferVertex(); 95 abstract string getGeometryBatchVertex(); 96 abstract string getSpriteBatchVertex(); 97 abstract string getBitmapTextVertex(); 98 } 99 abstract class FragmentShader 100 { 101 abstract string getDefaultFragment(); 102 abstract string getFrameBufferFragment(); 103 abstract string getGeometryBatchFragment(); 104 abstract string getSpriteBatchFragment(); 105 abstract string getBitmapTextFragment(); 106 } 107 108 abstract class ShaderProgram 109 { 110 string name; 111 } 112 113 114 public class Shader : IReloadable 115 { 116 VertexShader vertexShader; 117 FragmentShader fragmentShader; 118 ShaderProgram shaderProgram; 119 ShaderVariablesLayout[string] layouts; 120 protected ShaderVariablesLayout defaultLayout; 121 //Optional 122 IShader shaderImpl; 123 string fragmentShaderPath; 124 string vertexShaderPath; 125 126 protected string internalVertexSource; 127 protected string internalFragmentSource; 128 private bool isUseCall = false; 129 130 this(IShader shaderImpl) 131 { 132 this.shaderImpl = shaderImpl; 133 vertexShader = shaderImpl.createVertexShader(); 134 fragmentShader = shaderImpl.createFragmentShader(); 135 shaderProgram = shaderImpl.createShaderProgram(); 136 } 137 this(IShader shaderImpl, string vertexSource, string fragmentSource) 138 { 139 this(shaderImpl); 140 ShaderStatus status = loadShaders(vertexSource, fragmentSource); 141 if(status != ShaderStatus.SUCCESS) 142 { 143 import hip.console.log; 144 logln("Failed loading shaders"); 145 } 146 } 147 148 void setFromPreset(HipShaderPresets preset = HipShaderPresets.DEFAULT) 149 { 150 ShaderStatus status = ShaderStatus.SUCCESS; 151 fragmentShaderPath="hip.hiprenderer.backend."; 152 switch(HipRenderer.getRendererType()) 153 { 154 case HipRendererType.D3D11: 155 fragmentShaderPath~= "d3d.shader"; 156 break; 157 case HipRendererType.GL3: 158 fragmentShaderPath~= "gl3.shader"; 159 break; 160 case HipRendererType.METAL: 161 fragmentShaderPath~= "metal.shader"; 162 break; 163 default:break; 164 } 165 166 switch(preset) with(HipShaderPresets) 167 { 168 case SPRITE_BATCH: 169 fragmentShaderPath~= ".SPRITE_BATCH"; 170 status = loadShaders(vertexShader.getSpriteBatchVertex(), fragmentShader.getSpriteBatchFragment(), fragmentShaderPath); 171 break; 172 case FRAME_BUFFER: 173 fragmentShaderPath~= ".FRAME_BUFFER"; 174 status = loadShaders(vertexShader.getFrameBufferVertex(), fragmentShader.getFrameBufferFragment(), fragmentShaderPath); 175 break; 176 case GEOMETRY_BATCH: 177 fragmentShaderPath~= ".GEOMETRY_BATCH"; 178 status = loadShaders(vertexShader.getGeometryBatchVertex(), fragmentShader.getGeometryBatchFragment(), fragmentShaderPath); 179 break; 180 case BITMAP_TEXT: 181 fragmentShaderPath~= ".BITMAP_TEXT"; 182 status = loadShaders(vertexShader.getBitmapTextVertex(), fragmentShader.getBitmapTextFragment(), fragmentShaderPath); 183 break; 184 case DEFAULT: 185 fragmentShaderPath~= ".DEFAULT"; 186 status = loadShaders(vertexShader.getDefaultVertex(),fragmentShader.getDefaultFragment(), fragmentShaderPath); 187 break; 188 case NONE: 189 default: 190 break; 191 } 192 vertexShaderPath = fragmentShaderPath; 193 194 if(status != ShaderStatus.SUCCESS) 195 { 196 import hip.console.log; 197 logln("Failed loading shaders with status ", status, " at preset ", preset, " on "~fragmentShaderPath); 198 } 199 } 200 201 ShaderStatus loadShaders(string vertexShaderSource, string fragmentShaderSource, string shaderPath = "") 202 { 203 this.internalVertexSource = vertexShaderSource; 204 this.internalFragmentSource = fragmentShaderSource; 205 206 shaderProgram.name = shaderPath; 207 if(!shaderImpl.compileShader(vertexShader, vertexShaderSource)) 208 return ShaderStatus.VERTEX_COMPILATION_ERROR; 209 if(!shaderImpl.compileShader(fragmentShader, fragmentShaderSource)) 210 return ShaderStatus.FRAGMENT_COMPILATION_ERROR; 211 if(!shaderImpl.linkProgram(shaderProgram, vertexShader, fragmentShader)) 212 return ShaderStatus.LINK_ERROR; 213 // deleteShaders(); 214 return ShaderStatus.SUCCESS; 215 } 216 217 ShaderStatus loadShadersFromFiles(string vertexShaderPath, string fragmentShaderPath) 218 { 219 this.vertexShaderPath = vertexShaderPath; 220 this.fragmentShaderPath = fragmentShaderPath; 221 return loadShaders(getFileContent(vertexShaderPath), getFileContent(fragmentShaderPath)); 222 } 223 224 ShaderStatus reloadShaders() 225 { 226 return loadShadersFromFiles(this.vertexShaderPath, this.fragmentShaderPath); 227 } 228 229 void setVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset) 230 { 231 shaderImpl.sendVertexAttribute(layoutIndex, valueAmount, dataType, normalize, stride, offset); 232 } 233 234 235 /** 236 * If validateData is true, it will compare if the data has changed for choosing whether it should or not 237 send to the GPU. 238 * Params: 239 * name = 240 * val = 241 * validateData = 242 */ 243 public void setVertexVar(T)(string name, T val, bool validateData = false) 244 { 245 ShaderVar* v = tryGetShaderVar(name, ShaderTypes.VERTEX); 246 if(v != null) 247 { 248 v.set(val, validateData); 249 } 250 } 251 252 private ShaderVar* tryGetShaderVar(string name, ShaderTypes type) 253 { 254 import hip.util.conv:to; 255 bool isUnused; 256 ShaderVar* v = findByName(name, isUnused); 257 if(v is null) 258 { 259 if(!isUnused) 260 ErrorHandler.showWarningMessage("Shader " ~ type.to!string ~ " Var not set on shader loaded from '"~fragmentShaderPath~"'", 261 "Could not find shader var with name "~name~ 262 ((layouts.length == 0) ?". Did you forget to addVarLayout on the shader?" : 263 " Did you forget to add a layout namespace to the var name?" 264 )); 265 return null; 266 } 267 if(v.shaderType != type) 268 { 269 import hip.console.log; 270 logln = v.shaderType; 271 ErrorHandler.assertExit(false, "Variable named "~name~" must be from " ~ type.to!string ~ " Shader"); 272 } 273 return v; 274 } 275 /** 276 * If validateData is true, it will compare if the data has changed for choosing whether it should or not 277 send to the GPU. 278 * Params: 279 * name = 280 * val = 281 * validateData = 282 */ 283 public void setFragmentVar(T)(string name, T val, bool validateData = false) 284 { 285 ShaderVar* v = tryGetShaderVar(name, ShaderTypes.FRAGMENT); 286 if(v != null) 287 { 288 if(v.isBlackboxed) 289 { 290 if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val)) 291 v.isDirty = true; 292 } 293 else 294 v.set(val, validateData); 295 } 296 } 297 298 protected ShaderVar* findByName(string name, out bool isUnused) @nogc 299 { 300 int accessorSeparatorIndex = name.indexOf("."); 301 302 bool isDefault = accessorSeparatorIndex == -1; 303 if(isDefault) 304 { 305 ShaderVarLayout* sL = name in defaultLayout.variables; 306 if(sL !is null) 307 return sL.sVar; 308 isUnused = defaultLayout.isUnused(name); 309 } 310 else 311 { 312 ShaderVariablesLayout* l = (name[0..accessorSeparatorIndex] in layouts); 313 if(l !is null) 314 { 315 ShaderVarLayout* sL = name[accessorSeparatorIndex+1..$] in l.variables; 316 if(sL !is null) 317 return sL.sVar; 318 isUnused = l.isUnused(name[accessorSeparatorIndex+1..$]); 319 } 320 } 321 return null; 322 } 323 public ShaderVar* get(string name) 324 { 325 bool ignored; 326 return findByName(name, ignored); 327 } 328 329 330 public void addVarLayout(ShaderVariablesLayout layout) 331 { 332 ErrorHandler.assertLazyExit((layout.name in layouts) is null, "Shader: VariablesLayout '"~layout.name~"' is already defined"); 333 if(defaultLayout is null) 334 defaultLayout = layout; 335 layouts[layout.name] = layout; 336 layout.lock(this); 337 shaderImpl.createVariablesBlock(layout); 338 } 339 340 /** 341 * This creates a state in the current shader to which block will be accessed 342 * when using setVertexVar(".property"). If no default block is set ("") 343 * .property will always access the first block defined 344 * Params: 345 * blockName = Which block will be accessed with .property 346 */ 347 public void setDefaultBlock(string blockName){defaultLayout = layouts[blockName];} 348 349 void bind(){shaderImpl.bind(shaderProgram);} 350 void unbind(){shaderImpl.unbind(shaderProgram);} 351 void setBlending(HipBlendFunction src, HipBlendFunction dest, HipBlendEquation eq) 352 { 353 shaderImpl.setBlending(shaderProgram, src, dest, eq); 354 } 355 356 auto opDispatch(string member)() 357 { 358 static if(member == "useLayout") 359 { 360 isUseCall = true; 361 return this; 362 } 363 else 364 { 365 if(isUseCall) 366 { 367 setDefaultBlock(member); 368 isUseCall = false; 369 ShaderVar s; 370 return s; 371 } 372 return *defaultLayout.variables[member].sVar; 373 } 374 } 375 auto opDispatch(string member, T)(T value) 376 { 377 if(!defaultLayout.variables[member].sVar.set(value, false)) 378 { 379 ErrorHandler.assertExit(false, "Invalid value of type "~ 380 T.stringof~" passed to "~defaultLayout.name~"."~member); 381 } 382 } 383 384 void sendVars() 385 { 386 shaderImpl.sendVars(shaderProgram, layouts); 387 } 388 389 /** 390 * Bind array of textures. 391 * - This is handled a little different than simply blindly binding * to each slot. 392 * Since OpenGL, Direct3D and Metal handles that differently, a more general solution is required. 393 */ 394 void bindArrayOfTextures(IHipTexture[] textures, string varName) 395 { 396 shaderImpl.bindArrayOfTextures(shaderProgram, textures, varName); 397 } 398 399 400 protected void deleteShaders() 401 { 402 shaderImpl.deleteShader(&fragmentShader); 403 shaderImpl.deleteShader(&vertexShader); 404 } 405 406 bool reload() 407 { 408 vertexShader = shaderImpl.createVertexShader(); 409 fragmentShader = shaderImpl.createFragmentShader(); 410 shaderProgram = shaderImpl.createShaderProgram(); 411 412 return loadShaders(internalVertexSource, internalFragmentSource) == ShaderStatus.SUCCESS; 413 } 414 415 void onRenderFrameEnd() 416 { 417 shaderImpl.onRenderFrameEnd(shaderProgram); 418 } 419 420 }